
    window.onload = async () => {
    const env = {
        serviceUrl: "https://vjmap.com/server/api/v1",
        accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M",
        exampleMapId: "sys_zp",
        assetsPath: "../../assets/",
        ...__env__ // 如果您已私有化部署，需要连接已部署的服务器地址和token，请打开js/env.js,修改里面的参数
    };
    try {
        // 在线效果查看地址: https://vjmap.com/map3d/demo/#/demo/map/threejs/118threeWebglportal
        // --portal--
        // 下面代码参考threejs官方示例改写 https://threejs.org/examples/#webgl_portal
        let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
        let app = new vjmap3d.App(svc, {
            container: "map", // 容器id
            scene: {
                defaultLights: false
            },
            camera: {
                fov: 45,
                near: 1,
                far: 5000,
                position: [ 0, 75, 160  ]
            },
            control: {
                target: [0, 40, 0 ],
                minDistance: 10,
                maxDistance: 400
            }
        })
        let scene = app.scene, camera = app.camera, renderer = app.renderer;
        
        
        let smallSphereOne, smallSphereTwo;
        
        let portalCamera, leftPortal, rightPortal, leftPortalTexture, reflectedPosition,
            rightPortalTexture, bottomLeftCorner, bottomRightCorner, topLeftCorner;
        
        init();
        
        function init() {
        
            renderer.localClippingEnabled = true;
            renderer.toneMapping = THREE.ACESFilmicToneMapping;
        
        
            const planeGeo = new THREE.PlaneGeometry( 100.1, 100.1 );
        
            // bouncing icosphere
            const portalPlane = new THREE.Plane( new THREE.Vector3( 0, 0, 1 ), 0.0 );
            const geometry = new THREE.IcosahedronGeometry( 5, 0 );
            const material = new THREE.MeshPhongMaterial( {
                color: 0xffffff, emissive: 0x333333, flatShading: true,
                clippingPlanes: [ portalPlane ], clipShadows: true } );
            smallSphereOne = new THREE.Mesh( geometry, material );
            scene.add( smallSphereOne );
            smallSphereTwo = new THREE.Mesh( geometry, material );
            scene.add( smallSphereTwo );
        
            // portals
            portalCamera = new THREE.PerspectiveCamera( 45, 1.0, 0.1, 500.0 );
            scene.add( portalCamera );
            //frustumHelper = new THREE.CameraHelper( portalCamera );
            //scene.add( frustumHelper );
            bottomLeftCorner = new THREE.Vector3();
            bottomRightCorner = new THREE.Vector3();
            topLeftCorner = new THREE.Vector3();
            reflectedPosition = new THREE.Vector3();
        
            leftPortalTexture = new THREE.WebGLRenderTarget( 256, 256 );
            leftPortal = new THREE.Mesh( planeGeo, new THREE.MeshBasicMaterial( { map: leftPortalTexture.texture } ) );
            leftPortal.position.x = - 30;
            leftPortal.position.y = 20;
            leftPortal.scale.set( 0.35, 0.35, 0.35 );
            scene.add( leftPortal );
        
            rightPortalTexture = new THREE.WebGLRenderTarget( 256, 256 );
            rightPortal = new THREE.Mesh( planeGeo, new THREE.MeshBasicMaterial( { map: rightPortalTexture.texture } ) );
            rightPortal.position.x = 30;
            rightPortal.position.y = 20;
            rightPortal.scale.set( 0.35, 0.35, 0.35 );
            scene.add( rightPortal );
        
            // walls
            const planeTop = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xffffff } ) );
            planeTop.position.y = 100;
            planeTop.rotateX( Math.PI / 2 );
            scene.add( planeTop );
        
            const planeBottom = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xffffff } ) );
            planeBottom.rotateX( - Math.PI / 2 );
            scene.add( planeBottom );
        
            const planeFront = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0x7f7fff } ) );
            planeFront.position.z = 50;
            planeFront.position.y = 50;
            planeFront.rotateY( Math.PI );
            scene.add( planeFront );
        
            const planeBack = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xff7fff } ) );
            planeBack.position.z = - 50;
            planeBack.position.y = 50;
            //planeBack.rotateY( Math.PI );
            scene.add( planeBack );
        
            const planeRight = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0x00ff00 } ) );
            planeRight.position.x = 50;
            planeRight.position.y = 50;
            planeRight.rotateY( - Math.PI / 2 );
            scene.add( planeRight );
        
            const planeLeft = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xff0000 } ) );
            planeLeft.position.x = - 50;
            planeLeft.position.y = 50;
            planeLeft.rotateY( Math.PI / 2 );
            scene.add( planeLeft );
        
            // lights
            const mainLight = new THREE.PointLight( 0xe7e7e7, 2.5, 250, 0 );
            mainLight.position.y = 60;
            scene.add( mainLight );
        
            const greenLight = new THREE.PointLight( 0x00ff00, 0.5, 1000, 0 );
            greenLight.position.set( 550, 50, 0 );
            scene.add( greenLight );
        
            const redLight = new THREE.PointLight( 0xff0000, 0.5, 1000, 0 );
            redLight.position.set( - 550, 50, 0 );
            scene.add( redLight );
        
            const blueLight = new THREE.PointLight( 0xbbbbfe, 0.5, 1000, 0 );
            blueLight.position.set( 0, 50, 550 );
            scene.add( blueLight );
        
            app.signal.onAppUpdate.add(animate)
        
        }
        
        
        function renderPortal( thisPortalMesh, otherPortalMesh, thisPortalTexture ) {
        
            // set the portal camera position to be reflected about the portal plane
            thisPortalMesh.worldToLocal( reflectedPosition.copy( camera.position ) );
            reflectedPosition.x *= - 1.0; reflectedPosition.z *= - 1.0;
            otherPortalMesh.localToWorld( reflectedPosition );
            portalCamera.position.copy( reflectedPosition );
        
            // grab the corners of the other portal
            // - note: the portal is viewed backwards; flip the left/right coordinates
            otherPortalMesh.localToWorld( bottomLeftCorner.set( 50.05, - 50.05, 0.0 ) );
            otherPortalMesh.localToWorld( bottomRightCorner.set( - 50.05, - 50.05, 0.0 ) );
            otherPortalMesh.localToWorld( topLeftCorner.set( 50.05, 50.05, 0.0 ) );
            // set the projection matrix to encompass the portal's frame
            CameraUtils.frameCorners( portalCamera, bottomLeftCorner, bottomRightCorner, topLeftCorner, false );
        
            // render the portal
            thisPortalTexture.texture.colorSpace = renderer.outputColorSpace;
            renderer.setRenderTarget( thisPortalTexture );
            renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897
            if ( renderer.autoClear === false ) renderer.clear();
            thisPortalMesh.visible = false; // hide this portal from its own rendering
            renderer.render( scene, portalCamera );
            thisPortalMesh.visible = true; // re-enable this portal's visibility for general rendering
        
        }
        
        function animate() {
        
            // move the bouncing sphere(s)
            const timerOne = Date.now() * 0.01;
            const timerTwo = timerOne + Math.PI * 10.0;
        
            smallSphereOne.position.set(
                Math.cos( timerOne * 0.1 ) * 30,
                Math.abs( Math.cos( timerOne * 0.2 ) ) * 20 + 5,
                Math.sin( timerOne * 0.1 ) * 30
            );
            smallSphereOne.rotation.y = ( Math.PI / 2 ) - timerOne * 0.1;
            smallSphereOne.rotation.z = timerOne * 0.8;
        
            smallSphereTwo.position.set(
                Math.cos( timerTwo * 0.1 ) * 30,
                Math.abs( Math.cos( timerTwo * 0.2 ) ) * 20 + 5,
                Math.sin( timerTwo * 0.1 ) * 30
            );
            smallSphereTwo.rotation.y = ( Math.PI / 2 ) - timerTwo * 0.1;
            smallSphereTwo.rotation.z = timerTwo * 0.8;
        
            // save the original camera properties
            const currentRenderTarget = renderer.getRenderTarget();
            const currentXrEnabled = renderer.xr.enabled;
            const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
            renderer.xr.enabled = false; // Avoid camera modification
            renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
        
            // render the portal effect
            renderPortal( leftPortal, rightPortal, leftPortalTexture );
            renderPortal( rightPortal, leftPortal, rightPortalTexture );
        
            // restore the original rendering properties
            renderer.xr.enabled = currentXrEnabled;
            renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
            renderer.setRenderTarget( currentRenderTarget );
        
        }
        
        
    }
    catch (e) {
        console.error(e);
    }
};